package com.example.hutudshelf.security.config;

import com.example.hutudshelf.security.MyUserDetailsSercie;
import com.example.hutudshelf.security.filter.TokenFilter;
import com.example.hutudshelf.security.handle.NonAuthoritysHandle;
import com.example.hutudshelf.security.handle.NonloginOrTokenPastHandle;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

    private final MyUserDetailsSercie myUserDetailsSercie;
    private final TokenFilter tokenFilter;
    // 认证失败处理器
    private final NonloginOrTokenPastHandle nonloginOrTokenPastHandle;
    // 权限不足处理器
    private final NonAuthoritysHandle nonAuthoritysHandle;


    /**
     * AuthenticationManager是执行具体逻辑的接口,它里面定义了UserDetailsService配置和PasswordEncoder
     * 它通过调用UserDetailsService来获取用户信息
     * UserDetailsService获取信息流程：
     *  1. 对输入的用户名去数据库查询，如果存在则返回用户信息，如果不存在则返回null
     *  2. 然后调用PasswordEncoder来对输入的密码进行解析，如果解析成功则返回用户信息，否则返回null
     *  3. 这里使用的BCryptPasswordEncoder是将传入的密码进行BCrypt加密 然后和数据库查询出来的数据进行对比
     *     所以如果用这个解析器,那数据库传的密码格式就得是BCrypt格式的
     *
     * @author  xiaohu0305
     * @param passwordEncoder   密码解析
     * @time                    2025-4-18
     * @return                  org.springframework.security.authentication.AuthenticationManager
     */
    @Bean
    public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        //将编写的UserDetailsService注入进来
        provider.setUserDetailsService(myUserDetailsSercie);
        //将使用的密码编译器加入进来
        provider.setPasswordEncoder(passwordEncoder);
        //将provider放置到AuthenticationManager 中
        return new ProviderManager(provider);
    }

    /*
     * 在security安全框架中，提供了若干密码解析器实现类型。
     * 其中BCryptPasswordEncoder 叫强散列加密。可以保证相同的明文，多次加密后，
     * 密码有相同的散列数据，而不是相同的结果。
     * 匹配时，是基于相同的散列数据做的匹配。
     * Spring Security 推荐使用 BCryptPasswordEncoder 作为密码加密和解析器。
     * */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /*
     * 配置权限相关的配置
     * 安全框架本质上是一堆的过滤器，称之为过滤器链，每一个过滤器链的功能都不同
     * 设置一些链接不要拦截
     * */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        // 关闭csrf
        httpSecurity.csrf(AbstractHttpConfigurer::disable);
        httpSecurity.authorizeHttpRequests(
                it ->
                {
                    try {
                        it.requestMatchers("/sysLogin")
                                //设置登录路径所有人都可以访问
                                .permitAll()
                                .anyRequest()
                                .authenticated()
                                .and()
                                .exceptionHandling()
                                .authenticationEntryPoint(nonloginOrTokenPastHandle)
                                .accessDeniedHandler(nonAuthoritysHandle);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
        // 将TokenFilter添加到UsernamePasswordAuthenticationFilter之前
        httpSecurity.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
        return httpSecurity.build();
    }
}
